# 既有Haobtc的maker、taker赚手续费功能，也有普通的差价套利功能

import markets
import config
import math
import random
import time
import sys
import smtplib
from concurrent.futures import ThreadPoolExecutor
from operator import attrgetter
from lib.bmobHelper import BmobHelper

exchanges = []
init_btc = 0.0  # 程序运行前，比特币数量，在程序运行过程中，数量一般保持不变

total_cny = 0.0  # 当前的人民币数量和比特币数量
total_btc = 0.0

last_cny = 0.0  # 每轮操作前，人民币数量，每轮操作后都可能会变
last_btc = 0.0  # 上一轮操作结束后的比特币数量


def init_exchanges():
    exchange_names = config.exchanges
    global exchanges  # 哪里需要使用全局变量，哪里就声明一下
    for exchange_name in exchange_names:
        try:
            exec('import markets.' + exchange_name.lower())
            exchange = eval('markets.' + exchange_name.lower() + '.' + exchange_name + '()')
            exchanges.append(exchange)
            datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
            print("%s: Init exchange %s." % (datetime, exchange_name))
        except Exception as e:
            print("%s exchange name is invalid, please check config.py" % exchange_name)
            print(e)


# decimal表示浮点数值，number表示保留小数位数；number只能为1，2，3或者4
def retainDigiNum(decimal, number):  # 保留N位小数，不四舍五入；最好别用round函数，有精度误差的问题，会产生特别长的浮点数
    if number == 1:
        return int(decimal * 10) / 10
    if number == 2:
        return int(decimal * 100) / 100
    if number == 3:
        return int(decimal * 1000) / 1000
    if number == 4:
        return int(decimal * 10000) / 10000
    if number == 5:
        return int(decimal * 100000) / 100000
    if number == 6:
        return int(decimal * 1000000) / 1000000


def cancelAll():  # 不撤销好比特的订单
    global exchanges
    for exchange in exchanges:
        if exchange.__class__.__name__ == 'Haobtc':
            continue
        exchange.cancel_all()
    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
    print("%s: Cancel all orders except Haobtc!" % datetime)


def updateCurrentDepth(exclude=[]):  # 参数为当前操作不更新的市场列表
    global exchanges
    try:
        threadpool = ThreadPoolExecutor(max_workers=10)
        for exchange in exchanges:
            if exchange.__class__.__name__ in exclude:
                continue
            threadpool.submit(exchange.update_depth)
        threadpool.shutdown(wait=True)
    except Exception as e:
        # 若深度更新失败，将所有交易所深度信息置为0
        for exchange in exchanges:
            exchange.bid = 0.0
            exchange.bid_volume = 0.0
            exchange.ask = 0.0
            exchange.ask_volume = 0.0
        print('UpdateCurrentDepth Failed!' + str(e))


def updateCurrentAsset(exclude=[]):
    global exchanges
    try:
        threadpool = ThreadPoolExecutor(max_workers=10)
        for exchange in exchanges:
            if exchange.__class__.__name__ in exclude:
                continue
            threadpool.submit(exchange.update_accountInfo)
        threadpool.shutdown(wait=True)
    except Exception as e:
        # 若资产更新失败，将所有交易所的资产信息都置为0
        for exchange in exchanges:
            exchange.btc_free = 0.0
            exchange.btc_frozen = 0.0
            exchange.cny_free = 0.0
            exchange.cny_frozen = 0.0
        print('UpdateCurrentAsset Failed!' + str(e))

    global total_cny, total_btc
    total_btc = 0.0
    total_cny = 0.0
    for exchange in exchanges:
        total_cny += exchange.cny_free
        total_cny += exchange.cny_frozen
        total_btc += exchange.btc_free
        total_btc += exchange.btc_frozen


def getProfit():  # 计算当前总资产更上一周期的差值，即利润
    global total_cny, last_cny, total_btc, last_btc
    bid = 0
    for exchange in exchanges:
        if (exchange.ask == 0) or (exchange.bid == 0):
            continue
        bid = exchange.bid
        break

    updateCurrentAsset()
    if isNomal() and (bid != 0):  # 交易所资产正常并且参考价格不为零，才做利润统计
        return (total_cny - last_cny) + (total_btc - last_btc) * bid
    else:
        return 0


def balanceBtc(exclude=[]):  # 平衡一轮操作前后的BTC数量，保持BTC量前后一致
    global total_btc, total_cny, init_btc, last_btc, last_cny, exchanges
    updateCurrentAsset(exclude)

    # 获取当前比特币价格
    bid = 0
    for exchange in exchanges:
        if (exchange.ask == 0) or (exchange.bid == 0):
            continue
        bid = exchange.bid
        break

    if isNomal() and (abs((total_btc-last_btc)*bid+total_cny-last_cny) < init_btc * 20 * 2):
        # 交易所资产更新都成功即为正常
        # 并且当前与上一轮的总资产差值的绝对值小于阈值，此举是为了避免好比特的资产api更新异常而设
        diff = retainDigiNum(total_btc - init_btc, 4)
        temp = abs(diff)
        updateCurrentDepth()
        if diff <= -0.001:  # 假如不平衡量小于0.001，交易所都不能下单，故不处理
            record = BmobHelper()
            balance_exchanges = sorted(exchanges, key=attrgetter('ask'))
            for exchange in balance_exchanges:
                if exchange.__class__.__name__ == 'Haobtc':  # 不在Haobtc主动下单
                    continue
                if exchange.ask == 0:
                    continue
                if exchange.__class__.__name__ == 'Okcoin':
                    if temp < config.MinAmountOnce:
                        continue  # 如果需要平衡的量小于0.01，并且当前交易所为Okcoin，跳过不做
                    else:
                        temp = retainDigiNum(temp, 3)  # 如果小数位数大于3位，只保留3位

                if exchange.cny_free >= temp * (exchange.ask + config.SlidePrice):
                    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                    if exchange.buy(exchange.ask + config.SlidePrice, temp):
                        record.record_op(datetime, "enbalanceBTC", exchange.__class__.__name__, None,
                                         exchange.ask + config.SlidePrice, temp, None)
                        temp = 0
                if (exchange.cny_free < temp * (exchange.ask + config.SlidePrice)) and \
                        (retainDigiNum(exchange.cny_free / (exchange.ask + config.SlidePrice), 3) >= 0.01):
                    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                    if exchange.buy(exchange.ask + config.SlidePrice,
                                    retainDigiNum(exchange.cny_free / (exchange.ask + config.SlidePrice), 3)):
                        record.record_op(datetime, "enbalanceBTC", exchange.__class__.__name__, None,
                                         exchange.ask + config.SlidePrice,
                                         retainDigiNum(exchange.cny_free / (exchange.ask + config.SlidePrice), 3), None)
                        temp -= retainDigiNum(exchange.cny_free / (exchange.ask + config.SlidePrice), 3)
                if temp < 0.01:
                    break
            if temp >= 0.001:
                datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                print("%s: Exchanges(except Haobtc) has not enough RMB to balance. Should buy %f btc."
                      % (datetime, temp))

        if 0.001 <= diff:
            record = BmobHelper()
            balance_exchanges = sorted(exchanges, key=attrgetter('bid'), reverse=True)
            for exchange in balance_exchanges:
                if exchange.__class__.__name__ == 'Haobtc':
                    continue
                if exchange.bid == 0:
                    continue
                if exchange.__class__.__name__ == 'Okcoin':
                    if temp < config.MinAmountOnce:
                        continue  # 如果需要平衡的量小于0.01，并且当前交易所为Okcoin，跳过不做
                    else:
                        temp = retainDigiNum(temp, 3)  # 如果小数位数大于3位，只保留3位

                if exchange.btc_free >= temp:
                    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                    if exchange.sell(exchange.bid - config.SlidePrice, temp):
                        record.record_op(datetime, "enbalanceBTC", None, exchange.__class__.__name__,
                                         exchange.bid - config.SlidePrice, temp, None)
                        temp = 0
                if (exchange.btc_free < temp) and (exchange.btc_free >= 0.01):
                    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                    if exchange.sell(exchange.bid - config.SlidePrice, retainDigiNum(exchange.btc_free, 3)):
                        record.record_op(datetime, "enbalanceBTC", None, exchange.__class__.__name__,
                                         exchange.bid - config.SlidePrice, retainDigiNum(exchange.btc_free, 3), None)
                        temp -= retainDigiNum(exchange.btc_free, 3)
                if temp < 0.01:
                    break
            if temp >= 0.001:
                datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                print("%s: Exchanges has not enough BTC to balance. Should sell %f btc."
                      % (datetime, temp))
    else:
        datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
        print("%s: Exchanges asset fail to update." % datetime)


def isNomal():  # 判断交易所资产更新是否正常
    flag = True
    for exchange in exchanges:
        if (exchange.cny_free == 0) and (exchange.cny_frozen == 0) and (exchange.btc_free == 0) and \
                (exchange.btc_frozen == 0):
            flag = False
            break
    return flag


def isDepthNomal():  # 判断除好比特外的交易所深度是否正常
    flag = True
    for exchange in exchanges:
        if exchange.__class__.__name__ == 'Haobtc':
            continue
        if (exchange.ask == 0) or (exchange.bid == 0):
            flag = False
            break
    return flag


def handleHaobtc():  # 处理Haobtc的成交情况
    global exchanges
    # 作为参照时，ask, bid都不能为0；如果为零，跳过
    ask_min = 0
    ask_max = 0
    bid_min = 0
    bid_max = 0
    HaobtcPendingBidMaxVolume = 0
    HaobtcPendingAskMaxVolume = 0

    print("----------Haobtc----------")
    balanceBtc()

    updateCurrentAsset(["Haobtc"])  # 因为只是获取市场参考价格和深度，所以不更新Haobtc资产和深度
    updateCurrentDepth(["Haobtc"])

    if isDepthNomal():  # 除好比特以外的市场深度正常时，才继续下去；否则，不对好比特套利进行操作
        # 其他交易所的ask价即为平衡时的买入价，所以按ask价从低到高排列
        # 依次遍历，如果交易所的可用人民币大于1000元，则为ask最优价；取最后一个为次优价；若没有最优价，次优价也为最优价
        IsGetOptimal = False  # 是否得到最优标志
        for exchange in sorted(exchanges, key=attrgetter('ask'), reverse=False):
            if exchange.__class__.__name__ == 'Haobtc':
                continue
            if (exchange.ask == 0) or (exchange.bid == 0):
                continue
            if (exchange.cny_free > config.MinAmountOnce * exchange.ask) and (not IsGetOptimal):
                ask_min = exchange.ask
                HaobtcPendingAskMaxVolume = retainDigiNum(exchange.cny_free / exchange.ask, 4)
                IsGetOptimal = True
            ask_max = exchange.ask
        if not IsGetOptimal:  # 如果没有取得最优价，则使用次优价，这里指的是较高的ask
            ask_min = ask_max

        # 其他交易所的bid价即为平衡时的卖出价，所以按bid价从高到低排列
        # 依次遍历，如果交易所的可用比特币资产大于1000元，则为bid最优价；取最后一个为次优价；若没有最优价，次优价也为最优价
        IsGetOptimal = False
        for exchange in sorted(exchanges, key=attrgetter('bid'), reverse=True):
            if exchange.__class__.__name__ == 'Haobtc':
                continue
            if (exchange.ask == 0) or (exchange.bid == 0):
                continue
            if (exchange.btc_free > config.MinAmountOnce) and (not IsGetOptimal):
                bid_max = exchange.bid
                HaobtcPendingBidMaxVolume = retainDigiNum(exchange.btc_free, 4)
                IsGetOptimal = True
            bid_min = exchange.bid
        if not IsGetOptimal:  # 如果没有取得最优价，则使用次优价，这里指的是较低的bid
            bid_max = bid_min

        amountHaobtcBidPending = 0  # 统计Haobtc买单上的比特币累积数量
        amountHaobtcAskPending = 0  # 统计Haobtc卖单上的比特币累积数量
        for exchange in exchanges:
            if exchange.__class__.__name__ == 'Haobtc':
                exchange.update_accountInfo()
                exchange.update_depth()

                # 撤销不在下单范围的订单；撤销可能产生亏损的单；撤销在下单范围内的，但不是最优情况的订单
                orders = exchange.get_orders()
                if orders:
                    for order in orders:
                        if order['status'] != 'All deal':
                            if order['type'] == 'BUY':
                                if (bid_max != 0) and \
                                        (float(order['price']) * (1 - config.makerRate * config.ORDERWARNINGRATE) >= bid_max):
                                    # 买单没有利润，撤单
                                    exchange.cancel_order(order['order_id'])
                                    continue
                                if float(order['price']) < exchange.bid - config.OrderRetainScope + 1:
                                    # 买单不在挂单范围内，撤单
                                    exchange.cancel_order(order['order_id'])
                                    continue
                                if float(order['price']) >= exchange.bid - config.OrderRetainScope + 1:
                                    # 买单在保留范围内，但非最优
                                    if float(order['price']) <= (exchange.ask-1)-config.OrderValidScope:  # 在有效范围外面
                                        # 临界值使用次优价来计算，减少撤单概率
                                        if float(order['price']) <= (exchange.ask - 1) - config.OrderValidScope \
                                                < int(bid_min / (1 - config.makerRate * config.ORDERWARNINGRATE)):
                                            # 如果订单价格 <= 当前买一价 - 有效范围 < 临界值，撤单
                                            exchange.cancel_order(order['order_id'])
                                            continue
                                        elif float(order['price']) < int(
                                                        bid_min / (1 - config.makerRate * config.ORDERWARNINGRATE)) \
                                                <= (exchange.ask - 1) - config.OrderValidScope:
                                            # 如果订单价格 < 临界值向下取整 <= 当前买一价 - 有效范围，撤单
                                            exchange.cancel_order(order['order_id'])
                                            continue
                                    else:  # 在有效范围里面
                                        # 使用模拟退火算法来算当前撤单的概率
                                        if float(order['price']) \
                                                < (exchange.ask-1) \
                                                < int(bid_min / (1 - config.makerRate * config.ORDERWARNINGRATE)):
                                            if (1 - math.exp((float(order['price']) - (exchange.ask-1)
                                                              ) / config.OrderValidScope / 2)) > random.random():
                                                exchange.cancel_order(order['order_id'])
                                                continue
                                        if float(float(order['price'])) \
                                                < int(bid_min / (1 - config.makerRate * config.ORDERWARNINGRATE)) \
                                                <= (exchange.ask-1):
                                            if (1 - math.exp((float(order['price']) - int(
                                                        bid_min / (1 - config.makerRate * config.ORDERWARNINGRATE))
                                                              ) / config.OrderValidScope / 2)) > random.random():
                                                exchange.cancel_order(order['order_id'])
                                                continue

                                # 统计好比特当前已下买单的比特币数量。PS：只统计订单成交时的成本价在两个交易所之间的情况，
                                # 即Ok和火币差价大的情况
                                if bid_min < float(order['price']) * (1 - config.makerRate * config.ORDERWARNINGRATE) < bid_max:
                                    amountHaobtcBidPending += float(order['remainsize'])
                                    if amountHaobtcBidPending > HaobtcPendingBidMaxVolume:
                                        exchange.cancel_order(order['order_id'])
                                        amountHaobtcBidPending -= float(order['remainsize'])
                                        continue

                            elif order['type'] == 'SELL':
                                if (ask_min != 0) and \
                                        (float(order['price']) * (1 + config.makerRate * config.ORDERWARNINGRATE) <= ask_min):
                                    # 卖单没有利润，撤单
                                    exchange.cancel_order(order['order_id'])
                                    continue
                                if float(order['price']) > exchange.ask + config.OrderRetainScope - 1:
                                    # 卖单不在挂单范围内，撤单
                                    exchange.cancel_order(order['order_id'])
                                    continue
                                if float(order['price']) <= exchange.ask + config.OrderRetainScope - 1:  # 卖单在保留范围内，但非最优
                                    if float(order['price']) >= (exchange.bid+1)+config.OrderValidScope:  # 在有效范围外面
                                        # 临界值用次优价来算，减少撤单概率
                                        if math.ceil(ask_max / (1 + config.makerRate * config.ORDERWARNINGRATE)) \
                                                < (exchange.bid + 1) + config.OrderValidScope <= float(order['price']):
                                            # 临界值 < 当前卖一价+有效范围 <= 订单价格，撤单
                                            exchange.cancel_order(order['order_id'])
                                            continue
                                        elif (exchange.bid + 1) + config.OrderValidScope \
                                                <= math.ceil(ask_max / (1 + config.makerRate * config.ORDERWARNINGRATE)) \
                                                < float(order['price']):
                                            # 当前卖一价+有效范围 <= 临界值向上取整 < 订单价格，撤单
                                            exchange.cancel_order(order['order_id'])
                                            continue
                                    else:  # 在有效范围里面
                                        # 使用模拟退火算法来算当前撤单的概率
                                        if math.ceil(ask_max / (1 + config.makerRate * config.ORDERWARNINGRATE)) \
                                                < exchange.bid+1 \
                                                < float(order['price']):
                                            if (1-math.exp((exchange.bid+1-float(
                                                    order['price']))/config.OrderValidScope/2)) > random.random():
                                                exchange.cancel_order(order['order_id'])
                                                continue
                                        if exchange.bid+1 \
                                                <= math.ceil(ask_max / (1 + config.makerRate * config.ORDERWARNINGRATE)) \
                                                < float(order['price']):
                                            if (1-math.exp((math.ceil(ask_max/(
                                                        1+config.makerRate*config.ORDERWARNINGRATE)) - float(order['price'])
                                                            )/config.OrderValidScope/2)) > random.random():
                                                exchange.cancel_order(order['order_id'])
                                                continue

                                # 统计好比特当前已下卖单的比特币数量。PS：只统计订单成交时的成本价在两个交易所之间的情况，
                                # 即Ok和火币差价大的情况
                                if ask_min < float(order['price']) * (1 + config.makerRate * config.ORDERWARNINGRATE) < ask_max:
                                    amountHaobtcAskPending += float(order['remainsize'])
                                    if amountHaobtcAskPending > HaobtcPendingAskMaxVolume:
                                        exchange.cancel_order(order['order_id'])
                                        amountHaobtcAskPending -= float(order['remainsize'])
                                        continue

                # 如果Haobtc更新深度失败，即bid和ask为零，则不进行下单操作
                if (exchange.bid == 0) or (exchange.ask == 0):
                    break

                # 用对盘计算临界值，跟最优价和次优价对比，以主动寻求机会
                # Haobtc下买单时的正常情况
                if (exchange.cny_free > config.MinAmountOnce * exchange.ask) and (bid_min != 0) \
                        and ((exchange.ask-1) * (1 - config.makerRate * config.ORDERWARNINGRATE) < bid_min):
                    exchange.buy(int(exchange.ask-1), retainDigiNum(exchange.cny_free / (exchange.ask-1), 4))

                # Haobtc下卖单时的正常情况
                if (exchange.btc_free > config.MinAmountOnce) and (ask_max != 0) \
                        and ((exchange.bid+1) * (1 + config.makerRate * config.ORDERWARNINGRATE) > ask_max):
                    exchange.sell(int(exchange.bid+1), retainDigiNum(exchange.btc_free, 4))

                # 其他市场差价过大时，Haobtc下买单存在最优价的情况
                if (exchange.cny_free > config.MinAmountOnce * exchange.ask) \
                        and (amountHaobtcBidPending < HaobtcPendingBidMaxVolume - config.MinAmountOnce) \
                        and (bid_max != 0) \
                        and (bid_min <= (exchange.ask-1) * (1 - config.makerRate * config.ORDERWARNINGRATE) < bid_max):
                    # 下在买一上的数量
                    amount_pend_in_firstprice = min(HaobtcPendingBidMaxVolume,
                                                    retainDigiNum(exchange.cny_free / (exchange.ask-1), 4),
                                                    retainDigiNum(HaobtcPendingBidMaxVolume - amountHaobtcBidPending,
                                                                  4))
                    # 剩余的数量，下在次优价格对应的临界值上
                    amount_pend_in_next_price = exchange.cny_free / (exchange.ask-1) - amount_pend_in_firstprice
                    # 下买单
                    exchange.buy(int(exchange.ask-1), amount_pend_in_firstprice)
                    if amount_pend_in_next_price > config.MinAmountOnce:
                        exchange.buy(int(bid_min / (1 - config.makerRate * config.ORDERWARNINGRATE)),
                                     retainDigiNum(amount_pend_in_next_price, 4))

                # 其他市场最优价格不能产生利润时
                # 下买单，没有利润，下在临界值向下取整
                if (exchange.cny_free > config.MinAmountOnce * exchange.ask) \
                        and (amountHaobtcBidPending < HaobtcPendingBidMaxVolume - config.MinAmountOnce) \
                        and (bid_max != 0) \
                        and (bid_max < (exchange.ask-1) * (1 - config.makerRate * config.ORDERWARNINGRATE)):
                    if int(bid_max / (1 - config.makerRate * config.ORDERWARNINGRATE)) > exchange.bid - config.OrderRetainScope:
                        exchange.buy(int(bid_max / (1 - config.makerRate * config.ORDERWARNINGRATE)),
                                     min(HaobtcPendingBidMaxVolume,
                                         # 下全部空余资金
                                         retainDigiNum(
                                             exchange.cny_free / int(bid_max / (1 - config.makerRate * config.ORDERWARNINGRATE)),
                                             4),
                                         retainDigiNum(HaobtcPendingBidMaxVolume - amountHaobtcBidPending, 4)))

                # 其他市场差价过大时，Haobtc下卖单存在最优价的情况
                if (exchange.btc_free > config.MinAmountOnce) \
                        and (amountHaobtcAskPending < HaobtcPendingAskMaxVolume - config.MinAmountOnce) \
                        and (ask_max != 0) \
                        and (ask_min < (exchange.bid+1) * (1 + config.makerRate * config.ORDERWARNINGRATE) <= ask_max):
                    # 下在卖一上的数量
                    amount_pend_in_firstprice = min(HaobtcPendingAskMaxVolume,
                                                    retainDigiNum(exchange.btc_free, 4),
                                                    retainDigiNum(HaobtcPendingAskMaxVolume - amountHaobtcAskPending,
                                                                  4))
                    # 下在次优价格对应的临界值上的数量
                    amount_pend_in_next_price = exchange.btc_free - amount_pend_in_firstprice
                    # 下卖单
                    exchange.sell(int(exchange.bid+1), retainDigiNum(amount_pend_in_firstprice, 4))
                    if amount_pend_in_next_price > config.MinAmountOnce:
                        exchange.sell(math.ceil(ask_max / (1 + config.makerRate * config.ORDERWARNINGRATE)),
                                      retainDigiNum(amount_pend_in_next_price, 4))

                # 其他市场最优价格不能产生利润时
                # 下卖单，没有利润，下在临界值向上取整
                if (exchange.btc_free > config.MinAmountOnce) \
                        and (amountHaobtcAskPending < HaobtcPendingAskMaxVolume - config.MinAmountOnce) \
                        and (ask_max != 0) \
                        and ((exchange.bid+1) * (1 + config.makerRate * config.ORDERWARNINGRATE) < ask_min):
                    if math.ceil(ask_min / (1 + config.makerRate * config.ORDERWARNINGRATE))\
                            < exchange.ask + config.OrderRetainScope:
                        exchange.sell(math.ceil(ask_min / (1 + config.makerRate * config.ORDERWARNINGRATE)),
                                      min(HaobtcPendingAskMaxVolume,
                                          # 下全部空余资金
                                          retainDigiNum(exchange.btc_free, 4),
                                          retainDigiNum(HaobtcPendingAskMaxVolume - amountHaobtcAskPending, 4)))

                datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                print("%s: Pending Haobtc complete." % datetime)
                break
    else:
        datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
        print("%s: Pending Haobtc failed! Other exchanges' depth error!" % datetime)


def handleOthers():  # 处理普通的市场套利，也包括考虑了手续费的Haobtc
    global exchanges, init_btc, total_btc, total_cny, last_cny, last_btc
    # 标记利润来源，0表示来源于被动策略，1表示来源于主动策略；初始为0
    flag = 0
    record = BmobHelper()

    print("----------Others----------")
    if config.OpenPriorityStrategy:
        datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
        updateCurrentAsset(["Haobtc"])
        if not isNomal():
            # 如果交易所资产更新失败，或者handleOthers跟handleHaobtc之中haobtc有成交，或者上一轮平衡币数出错，则操作之前，平衡一次
            # balanceBtc() # 考虑不用平衡
            datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
            updateCurrentAsset(["Haobtc"])
            print("%s: Asset is updated! Current total_cny: %f, total_btc: %f; Init_btc(changeless): %f"
                % (datetime, total_cny, total_btc, init_btc))
        else:
            print("%s: Asset is updated! Current total_cny: %f, total_btc: %f; Init_btc(changeless): %f"
                % (datetime, total_cny, total_btc, init_btc))

        datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
        updateCurrentDepth(["Haobtc"])
        print("%s: Depth is updated!" % datetime)
        for exchange in exchanges:
            print("%8s, bid: %7.2f, ask: %7.2f;  btc_free: %10.6f, btc_frozen: %10.6f, cny_free: %9.4f, cny_frozen: %9.4f" %
                (exchange.__class__.__name__, exchange.bid, exchange.ask, exchange.btc_free, exchange.btc_frozen,
                exchange.cny_free, exchange.cny_frozen))

        buy_exchanges = sorted(exchanges, key=attrgetter('ask'), reverse=False)
        sell_exchanges = sorted(exchanges, key=attrgetter('bid'), reverse=True)

        for buy_exchange in buy_exchanges:
            for sell_exchange in sell_exchanges:
                if buy_exchange == sell_exchange:  # 排除相同的交易所
                    continue
                if (buy_exchange.__class__.__name__ == 'Haobtc') or (sell_exchange.__class__.__name__ == 'Haobtc'):
                    continue
                if buy_exchange.bid == 0 or sell_exchange.bid == 0:  # 排除交易所深度更新不成功；只要更新不成功，四个参数都为0，取其中任一个判断
                    continue
                if sell_exchange.bid * (1 - sell_exchange.fee / 100) - buy_exchange.ask * \
                        (1 + buy_exchange.fee / 100) < config.MinDiff:  # 排除价差不够
                    continue  # 思考：能不能简化步骤，将分析和买卖合并
                maxVolume = max(config.MinAmountOnce, min(retainDigiNum(buy_exchange.ask_volume, 3),
                                                        retainDigiNum(sell_exchange.bid_volume, 3)))  # 最大可交易量
                price = retainDigiNum((sell_exchange.bid + buy_exchange.ask) / 2, 2)

                if (buy_exchange.cny_free < config.MinAmountOnce * price) or \
                        (sell_exchange.btc_free < config.MinAmountOnce):  # 排除可用资金不够
                    continue

                volume = min(config.MaxAmountOnce, retainDigiNum(buy_exchange.cny_free / price, 3),
                            retainDigiNum(sell_exchange.btc_free, 3), maxVolume)  # 实际下单币数取交易所可交易数，最大可交易数，最大交易数限制，四者最小值

                datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))  # 记录买卖操作的时间
                threadpool = ThreadPoolExecutor(max_workers=2)
                buy_future = threadpool.submit(buy_exchange.buy, price, volume)
                sell_future = threadpool.submit(sell_exchange.sell, price, volume)
                try:
                    buy_result = buy_future.result(timeout=10)
                    sell_result = sell_future.result(timeout=10)
                except Exception as e:
                    buy_result = False
                    sell_result = False
                    print(e)

                record.record_op(datetime, "trade", buy_exchange.__class__.__name__, sell_exchange.__class__.__name__,
                                price, volume, "Price diff: %f" % (sell_exchange.bid - buy_exchange.ask))

                if buy_exchange.cancel_order(buy_result):
                    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                    record.record_op(datetime, "error", None, None, 0, 0,
                                    "%s dot not completely deal" % buy_exchange.__class__.__name__)
                if sell_exchange.cancel_order(sell_result):
                    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
                    record.record_op(datetime, "error", None, None, 0, 0,
                                    "%s dot not completely deal" % sell_exchange.__class__.__name__)
                balanceBtc()

                flag = 1
                break
            if flag == 1:
                break
        if flag == 0:
            datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
            print("%s: No opportunity to arbitrage." % datetime)

    print('----------Profit----------')
    if int(time.time()) % config.PeriodRecordProfit < config.TiggerTime:
        datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
        profit = retainDigiNum(getProfit(), 6)  # 此操作会更新资产信息

        if profit != 0:  # 如果利润值为0，上一次的比特币数量和人民币数量不变化；利润值不为零，才用当前的数量取代上次的
            # 计算利润时利润值为零的情况：交易所资产更新失败；交易所深度更新失败；利润值真的为零
            last_cny = total_cny  # 操作结束后，将当前的人民币（和比特币）数量赋值给last_cny，作为下一轮操作的利润统计参考值
            last_btc = total_btc

        # 获取当前市场价格
        price = 0
        for exchange in exchanges:
            if (exchange.ask == 0) or (exchange.bid == 0):
                continue
            price = exchange.bid
            break

        if isNomal() and (price != 0) and (abs(profit) < total_btc * 20 * 3):  # 预估最大差价为20，误差系数为3
            # flag记录利润来源，0表示好比特，1表示其他市场
            record.record_op(datetime, "profit", None, None, flag, retainDigiNum(profit, 6),
                             "total_cny: %.6f, total_btc: %.6f, price: %.2f" % (total_cny, total_btc, price))

        print("%s: Current total_cny: %f, total_btc: %f; profit: %f" % (datetime, total_cny, total_btc, profit))
    print("****************************************")


def balanceFund():
    if (int(time.time() + 28800) % 86400 % int(24 / config.TIMES_BALANCEFUND * 3600) < config.TiggerTime) \
            and isNomal():
        # 撤销非好比特市场的所有订单，因为行情大波动时，实际滑点大于预估滑点，导致订单长时间地挂在不能成交的位置
        cancelAll()
        # 用来平衡市场的比特币和人民币价值，使之保持平衡
        global total_btc, total_cny, init_btc, last_btc, last_cny, exchanges
        ask = 0

        for exchange in exchanges:
            if (exchange.ask == 0) or (exchange.bid == 0):
                continue
            ask = exchange.ask
            break

        referPrice = ask  # 参考价格
        referBTC = (referPrice*total_btc + total_cny) / 2 / referPrice  # 比特币参考持仓
        fundBalance = referBTC - init_btc  # 需要平衡的币数，根据当前资产来算的
        fundDiff = fundBalance * referPrice  # 需要平衡的比特币数量的人民币市值

        # 资产差超过了资金触发阈值，并且参考价格不为零，才做资金平衡操作
        if abs(fundDiff) > config.ThresholdPrice * total_btc and (referPrice != 0):
            if fundBalance > 0:  # 比特币跌了，留一定的缓冲区更低的时候买
                referBTC -= (config.ThresholdPrice * total_btc)/referPrice
            else:  # 比特币涨了，留一定的缓冲区更高的时候卖
                referBTC += (config.ThresholdPrice * total_btc)/referPrice

            init_btc = retainDigiNum(referBTC, 4) # 用当前的持仓参考值覆盖之前的初始比特币数量

            balanceBtc()

            # 记录操作
            datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
            record = BmobHelper()
            # 操作比特币数量记录在amount字段，正为买入比特币，负为卖出比特币；折合人民币记录在price字段
            record.record_op(datetime, "fundBalance", None, None, retainDigiNum(fundDiff, 4),
                             retainDigiNum(fundBalance, 4), None)


def sendEmail():
    # 邮件用来报告错误信息
    subject = 'Error report!'
    datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
    msg = "Arbitrager exit! Please restart."
    message = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s\r\n" % \
              (config.EMAIL_HOST_USER, ", ".join(config.EMAIL_RECEIVER), subject, msg)
    try:
        smtpserver = smtplib.SMTP(config.EMAIL_HOST)
        smtpserver.set_debuglevel(0)
        smtpserver.ehlo()
        smtpserver.starttls()
        smtpserver.login(config.EMAIL_HOST_USER, config.EMAIL_HOST_PASSWORD)
        smtpserver.sendmail(config.EMAIL_HOST_USER, config.EMAIL_RECEIVER, message)
        smtpserver.quit()
        smtpserver.close()
        print("%s: Send email successfully!" % datetime)
    except Exception as e:
        print(e)
        print("%s: Send email failed!" % datetime)


def onTick():
    global total_cny

    handleHaobtc()
    handleOthers()


def main():
    global init_btc, total_btc, last_cny, total_cny, last_btc
    init_exchanges()
    updateCurrentAsset()
    if isNomal():
        last_cny = total_cny  # 记录程序运行前，人民币数量，给第一次onTick使用，每次onTick执行完后，last_cny都重新赋值
        last_btc = total_btc
        init_btc = total_btc  # 记录程序运行前，比特币的数量，此数量一直不变
    else:
        print("Update asset failed! Init program failed! Please find the problems and restart.")
        sys.exit()
    cancelAll()
    while True:
        onTick()
        balanceFund()
        time.sleep(config.TickInterval)


if __name__ == '__main__':
    main()

